package com.ynu.springbootappdemo.common.utils;

import cn.hutool.core.util.ObjectUtil;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.ynu.springbootappdemo.common.exception.BusinessException;
import com.ynu.springbootappdemo.dao.IUserDao;
import com.ynu.springbootappdemo.model.entity.User;
import com.ynu.springbootappdemo.model.vo.TokenVo;
import com.ynu.springbootappdemo.service.TokenService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.Date;

import static cn.hutool.core.lang.Console.log;
import static com.ynu.springbootappdemo.common.constant.CommonConstant.ACCESS_TOKEN;
import static com.ynu.springbootappdemo.common.constant.CommonConstant.REFRESH_TOKEN;

@Component
@Slf4j
public class TokenUtils {
    private static IUserDao staticUserDao;
    //密钥
    private static final String SECRET = "ynu.edu.lcy";

    @Resource
    private IUserDao userDao;

    @Resource
    private TokenService tokenService;

    static long ACCESS_Token_EXPIRE_TIME = 7;
    static long REFRESH_Token_EXPIRE_TIME = 30;

    @PostConstruct
    public void setAdminService() {
        staticUserDao = userDao;
    }

    //生成token
    public static String createToken(User user, String tokenType) {
        return JWT.create().withAudience(user.getUserPhone()) //将userid保存到token里面作为载荷
                .withIssuedAt(new Date())
                //如果是accessToken，设置7天过期，如果是refreshToken，设置30天
                .withExpiresAt(new Date(System.currentTimeMillis() + (tokenType.equals(ACCESS_TOKEN) ?
                        ACCESS_Token_EXPIRE_TIME : REFRESH_Token_EXPIRE_TIME) * 24 *
                        60 * 60 * 1000))
                .withIssuer("lcy") //签发人
                .withClaim("role", user.getUserRole())
                .withClaim("tokenType", tokenType)
                .sign(Algorithm.HMAC256(SECRET));
    }

    /**
     * 获取Token
     */
    public String getToken(HttpServletRequest request) {
        //获取token
        String token = request.getHeader("token");
        if (StringUtils.isEmpty(token)) {
            token = request.getParameter("token");
        }
        if (StringUtils.isEmpty(token)) {
            throw new BusinessException(ErrorCode.NOT_LOGIN_ERROR);
        }
        return token;
    }

    /**
     * 校验token合法性
     *
     * @param token
     * @return DecodedJWT
     */
    private DecodedJWT validateToken(String token, String needTokenType) {
        DecodedJWT decodedJWT;
        if (StringUtils.isBlank(token)) {
            throw new BusinessException(ErrorCode.NOT_LOGIN_ERROR);
        }
        // 1、只判断token是否有效以及是否被修改
        try {
            //判断有效性
            JWTVerifier verifier = JWT.require(Algorithm.HMAC256(SECRET))
                    .withIssuer("lcy")
                    .build();
            decodedJWT = verifier.verify(token);
        } catch (JWTVerificationException e) {
            //token无效
            throw new BusinessException(ErrorCode.TOKEN_INVALID_ERROR);
        }catch (Exception e){
            e.printStackTrace();
            throw new BusinessException(ErrorCode.TOKEN_EXPIRED_ERROR);
        }
        // 2、判断该token是否本系统的凭证
        //获取tokenType
        String tokenType = decodedJWT.getClaim("tokenType").asString();
        if (StringUtils.isBlank(tokenType) || !StringUtils.equals(tokenType, needTokenType)) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        //获取缓存中的token
        String userPhone = decodedJWT.getAudience().get(0);
        String cachedToken = tokenService.getCachedToken(userPhone, tokenType);
        // 判断是否已过期
        if (StringUtils.isBlank(cachedToken) || ObjectUtil.isEmpty(cachedToken)) {
            //如果accessToken过期，则获取最新token
            if (tokenType.equals(ACCESS_TOKEN)) {
                throw new BusinessException(ErrorCode.TOKEN_EXPIRED_ERROR);
            } else {
                //如果refreshToken过期，则说明登录过期需要重新登陆
                throw new BusinessException(ErrorCode.NOT_LOGIN_ERROR);
            }
        }
        //判断是否一致
        if (!cachedToken.equals(token)) {
            throw new BusinessException(ErrorCode.TOKEN_INVALID_ERROR);
        }
        return decodedJWT;

    }

    /**
     * 通过解析token返回当前用户
     */
    public User getCurrentUser(HttpServletRequest request, String tokenType) {
        //请求头或请求体获取token
        String token = getToken(request);
        DecodedJWT decodedJWT = validateToken(token, tokenType);
        String userPhone = decodedJWT.getAudience().get(0);
        log("userPhone: " + userPhone);
        return staticUserDao.findUserByUserPhone(userPhone);
    }

    /**
     * 解析用户角色
     */
    public String getCurrentUserRole(HttpServletRequest request, String tokenType) {
        String token = getToken(request);
        DecodedJWT decodedJWT = validateToken(token, tokenType);
        return decodedJWT.getClaim("role").asString();
    }

    /**
     * 把accessToken和refreshToken存入缓存
     */
    public void saveTokenToRedis(User user, String accessToken, String refreshToken) {
        //存入缓存
        tokenService.updateCachedToken(user, accessToken, ACCESS_TOKEN);
        tokenService.updateCachedToken(user, refreshToken, REFRESH_TOKEN);
    }

    /**
     * 通过refresh_token换取新的access_token
     */
    public TokenVo updateAccessToken(HttpServletRequest request) {
        //解析其中用户
        User user = getCurrentUser(request, REFRESH_TOKEN);
        //生成新的access_token
        String newAccessToken = createToken(user, ACCESS_TOKEN);
        //生成新的refresh_token
        String newRefreshToken = createToken(user, REFRESH_TOKEN);
        //存入redis
        saveTokenToRedis(user, newAccessToken, newRefreshToken);
        return new TokenVo(newAccessToken, newRefreshToken);
    }
}
